#pragma once

// thrust, includes
#include <thrust/graph/device_adjacency_list.hpp>

// boost, includes
#include <boost/graph/adjacency_list.hpp>
#include <boost/mpl/if.hpp>

// stl, includes
#include <iostream>
#include <vector>

// system, includes
#include <time.h>

using namespace std;

namespace thrust {
  
  typedef thrust::detail::edge_desc_impl<thrust::bidirectional_tag,size_t> bidir_edge;
  typedef thrust::detail::edge_desc_impl<thrust::undirected_tag,size_t> undir_edge;

  template <class Graph>
  struct boost_serialize_traits {
    
    typedef typename boost::is_undirected_graph<Graph>    UnDirectedT;
    typedef typename boost::is_directed_graph<Graph>      DirectedT;
    typedef typename boost::is_bidirectional_graph<Graph> BidirectionalT;
    
    typedef typename boost::mpl::if_<BidirectionalT
                                     , typename boost::mpl::if_<DirectedT
                                                                , thrust::bidirectional_tag
                                                                , thrust::undirected_tag
                                                                >::type
                                     , directed_tag
                                     >::type directed_category;

    typedef typename boost::mpl::if_<BidirectionalT
                                     , typename boost::mpl::if_<DirectedT
                                                                , thrust::bidir_stored_vertex
                                                                , thrust::stored_vertex
                                                                >::type
                                     , stored_vertex
                                     >::type StoredVertex;

    typedef thrust::stored_edge StoredEdge;
    typedef thrust::detail::edge_desc_impl<directed_category,std::size_t> edge_descriptor;
    
    typedef std::vector<StoredVertex>    vertices_t;
    typedef std::vector<StoredEdge>      out_edges_t;
    typedef std::vector<StoredEdge>      in_edges_t;
    typedef std::vector<edge_descriptor> edges_t;
  };
  
  template <class Graph
            , class StoredVertex
            , class StoredEdge
            >
  void boost_serialize( const Graph& _g,
                        std::vector<StoredVertex>& vertices,
                        std::vector<StoredEdge>& outedges
                        )
  {
    clock_t start, finish;

    cout <<"start selializing " <<flush;
    start =clock();

    typedef typename boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
    typedef typename boost::graph_traits<Graph>::vertex_iterator   vertex_iterator;
    typedef typename boost::graph_traits<Graph>::out_edge_iterator out_edge_iterator;
    
    Graph& g =const_cast<Graph&>(_g);
    
    typename boost::property_map<Graph,boost::vertex_index_t>::type index 
      =boost::get(boost::vertex_index,g);
    
    std::size_t offset =0;
    vertex_iterator viter, vlast;
    boost::tie( viter, vlast) =boost::vertices( g);
    for( ; viter !=vlast; ++viter){
      StoredVertex sv;
      sv.setOutOffset( offset);
      sv.setOutDegree( boost::out_degree( *viter, g));
      
      vertices.push_back( sv);
      
      offset +=sv.getOutDegree();
      
      out_edge_iterator out_iter, out_last;
      boost::tie( out_iter, out_last) =boost::out_edges( *viter, g);
      for( ; out_iter !=out_last; ++out_iter){
        vertex_descriptor v =boost::target(*out_iter, g);
        StoredEdge se( index[v]);
        outedges.push_back( se);
      }
    }
    
    finish =clock();
    cout <<" ok! " 
         <<(double)(finish-start)/(double)CLOCKS_PER_SEC <<"[sec]\n" <<endl;
  }
 
  template <class Graph
            , class StoredVertex
            , class StoredEdge
            >
  void boost_serialize( const Graph& _g, 
                        std::vector<StoredVertex>& vertices, 
                        std::vector<StoredEdge>& outedges,
                        std::vector<undir_edge>& edges)
  {
    clock_t start, finish;

    cout <<"start selializing " <<flush;
    start =clock();

    typedef typename boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
    typedef typename boost::graph_traits<Graph>::vertex_iterator   vertex_iterator;
    typedef typename boost::graph_traits<Graph>::out_edge_iterator out_edge_iterator;
    typedef typename boost::graph_traits<Graph>::edge_descriptor edge_descriptor;
    typedef typename boost::graph_traits<Graph>::edge_iterator   edge_iterator;
    
    Graph& g =const_cast<Graph&>(_g);
    
    typename boost::property_map<Graph,boost::vertex_index_t>::type index 
      =boost::get(boost::vertex_index,g);
    
    std::size_t offset =0;
    vertex_iterator viter, vlast;
    boost::tie( viter, vlast) =boost::vertices( g);
    for( ; viter !=vlast; ++viter){
      StoredVertex sv;
      sv.setOutOffset( offset);
      sv.setOutDegree( boost::out_degree( *viter, g));
      
      vertices.push_back( sv);
      
      offset +=sv.getOutDegree();
      
      out_edge_iterator out_iter, out_last;
      boost::tie( out_iter, out_last) =boost::out_edges( *viter, g);
      for( ; out_iter !=out_last; ++out_iter){
        vertex_descriptor v =boost::target(*out_iter, g);
        StoredEdge se( index[v]);
        outedges.push_back( se);
      }
    }

    edge_iterator eiter, elast;
    boost::tie( eiter, elast) =boost::edges( g);
    for( ; eiter !=elast; ++eiter){
      undir_edge e( index[boost::source(*eiter, g)],
                    index[boost::target(*eiter, g)]);
      edges.push_back( e);
    }

    finish =clock();
    cout <<" ok! " 
         <<(double)(finish-start)/(double)CLOCKS_PER_SEC <<"[sec]\n" <<endl;
  }
  
  template <class Graph
            , class StoredVertex
            , class StoredEdge
            >
  void boost_serialize( const Graph& _g, 
                        std::vector<StoredVertex>& vertices, 
                        std::vector<StoredEdge>& outedges,
                        std::vector<StoredEdge>& inedges,
                        std::vector<bidir_edge>& edges)
  {
    clock_t start, finish;

    cout <<"start selializing " <<flush;
    start =clock();

    typedef typename boost::graph_traits<Graph>::vertex_descriptor vertex_descriptor;
    typedef typename boost::graph_traits<Graph>::vertex_iterator   vertex_iterator;
    typedef typename boost::graph_traits<Graph>::out_edge_iterator out_edge_iterator;
    typedef typename boost::graph_traits<Graph>::in_edge_iterator  in_edge_iterator;
    typedef typename boost::graph_traits<Graph>::edge_descriptor edge_descriptor;
    typedef typename boost::graph_traits<Graph>::edge_iterator   edge_iterator;

    Graph& g =const_cast<Graph&>(_g);

    typename boost::property_map<Graph,boost::vertex_index_t>::type index 
      =boost::get(boost::vertex_index,g);
    
    std::size_t out_offset =0;
    std::size_t in_offset  =0;
    vertex_iterator viter, vlast;
    boost::tie( viter, vlast) =boost::vertices( g);
    for( ; viter !=vlast; ++viter){

      StoredVertex sv;

      sv.setOutOffset( out_offset);
      sv.setOutDegree( boost::out_degree( *viter, g));

      sv.setInOffset( in_offset);
      sv.setInDegree( boost::in_degree( *viter, g));

      vertices.push_back( sv);
      
      out_offset +=sv.getOutDegree();
      in_offset  +=sv.getInDegree();

      out_edge_iterator out_iter, out_last;
      boost::tie( out_iter, out_last) =boost::out_edges( *viter, g);
      for( ; out_iter !=out_last; ++out_iter){
        vertex_descriptor v =boost::target(*out_iter, g);
        StoredEdge se( index[v]);
        outedges.push_back( se);
      }
      
      in_edge_iterator in_iter, in_last;
      boost::tie( in_iter, in_last) =boost::in_edges( *viter, g);
      for( ; in_iter !=in_last; ++in_iter){
        vertex_descriptor u =boost::source(*in_iter, g);
        StoredEdge se( index[u]);
        inedges.push_back( se);
      }     
    } // end vertices

    edge_iterator eiter, elast;
    boost::tie( eiter, elast) =boost::edges( g);
    for( ; eiter !=elast; ++eiter){
      bidir_edge e( index[boost::source(*eiter, g)],
                    index[boost::target(*eiter, g)]);
      edges.push_back( e);
    }

    finish =clock();
    cout <<" ok! " 
         <<(double)(finish-start)/(double)CLOCKS_PER_SEC <<"[sec]\n" <<endl;
  }
}
